/* A COMPLETER - Pef, 7 avril
	Di-Costanzo Jonathan
	Fauché Pierre-Élie
	3TC 3
*/

#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <unistd.h>

#include "p2p_common.h"
#include "p2p_addr.h"
#include "p2p_msg.h"
#include "p2p_file.h"
#include "p2p_share.h"
#include "p2p_research.h"
#include "p2p_network.h" //pour les structures de découverte de topologie

// PID du fils créé pour récupérer un fichier
int pidFils=-1;

/****************************************************
Suppression de la structure server_params
****************************************************/
void sp_delete(server_params *sp) {
	p2p_addr_delete(sp->p2pMyId);
  p2p_addr_delete(sp->neighbors.left);
  p2p_addr_delete(sp->neighbors.right);
  p2p_search_delete(sp->p2pSearchFaits);
  p2p_search_delete(sp->p2pSearchVus);
  p2p_reply_delete(sp->p2pResultats);
  p2p_network_delete(sp);
}

/***************************************************************************
met les offsets du message msg aux valeurs indiquées
***************************************************************************/
int p2p_get_set_msg_offsets(p2p_msg msg, unsigned long int begin, unsigned long int end) {
	
	begin=htonl(begin);
	end=htonl(end);
	memcpy(p2p_get_payload(msg), &begin, P2P_INT_SIZE);
	memcpy(&(p2p_get_payload(msg)[P2P_INT_SIZE]), &end, P2P_INT_SIZE);

	return P2P_OK;
}

/***************************************************************************
donne le STATUS CODE d'un message P2P_DATA
Ou FF si le message n'est pas un P2P_DATA
***************************************************************************/
unsigned char p2p_data_msg_get_status(p2p_msg msg) {
	//on vérifie si le message est un P2P_DATA
	if ( p2p_msg_get_type(msg)==P2P_MSG_DATA ) {
		return p2p_get_payload(msg)[0];
	}
	return 0xFF;
}

/***************************************************************************
donne la VALUE d'un message P2P_DATA
***************************************************************************/
unsigned long int p2p_data_msg_get_value(p2p_msg msg) {
	
	unsigned long int value;
	memcpy(&value, &(p2p_get_payload(msg)[P2P_INT_SIZE]), P2P_INT_SIZE);
	value=ntohl(value);
	
	return value;
}

/***************************************************************************
Récupère un fichier
***************************************************************************/
int p2p_get_data(server_params *sp, p2p_msg get_msg, const char *file, const long int taille, int numero_get) {
	
	long int pourcentage=0;
	long int begin_offset=0, end_offset=0;
	//unsigned char *octets_data;
	p2p_msg data_msg = p2p_msg_create();
	
	fd_set rfds; // contiendra la socket tcp
  struct timeval tv;
  
 	while ( begin_offset<taille ) {
 		
		//pas plus de 64KB (=65536 octets) par message. On garde une marge, à voir.
		end_offset = ( (taille-begin_offset)>64000 ) ? begin_offset+64000-1 : taille-1 ;
		
		//on rentre les offsets dans le message
		p2p_get_set_msg_offsets(get_msg, begin_offset, end_offset);
		
		//ouverture socket vers noeud avec qui on fait l'échange
		int socket=p2p_tcp_socket_create(sp, p2p_msg_get_dst(get_msg));
		
		//on peut envoyer le message P2P_GET
		p2p_tcp_msg_sendfd(sp, get_msg, socket);
		
		//on initialise le set
  	FD_ZERO(&rfds); 
  	FD_SET(socket,&rfds); 
  	// Attends jusqu'à 5 minutes la réception d'un message data. 
  	tv.tv_sec = 5*60;
  	tv.tv_usec = 0;
		
		//on attend la réception un certain temps
		select(socket+1, &rfds, NULL, NULL, &tv);
		if (FD_ISSET(socket, &rfds)) {
			p2p_tcp_msg_recvfd(sp, data_msg, socket);
			
			if (p2p_data_msg_get_status(data_msg)==P2P_DATA_OK) {
				//récupération du data
				
				if (p2p_file_set_chunck(sp, file, (int)begin_offset, (int)end_offset, &(p2p_get_payload(data_msg)[2*P2P_INT_SIZE]))!=P2P_OK) {
					//l'écriture dans le fichier s'est mal passée
					//afficher kke chose et sortir proprement de la fonction sans tout planter
					//...
				}
				
				//tout s'est bien passé, on bouge le begin_offset
				begin_offset=end_offset+1;
				
				//on update da progression sur la GUI
				pourcentage=(long int)(((double)begin_offset/(double)taille)*100);
				VERBOSE(sp,GUI,"GET::UPDATE::%d::%ld\n", numero_get, pourcentage);
				
			} else if (p2p_data_msg_get_status(data_msg)==P2P_DATA_ERROR) {
				//on affiche l'erreur et on sort gentiment
				//...
				break;
			} else {
				//le type du message n'est pas bon
				//on affiche l'erreur et on sort gentiment
				//...
				break;
			}
			
		} else {
			//le DATA demandé n'a pas été envoyé assez rapidement
			//on affiche kke chose et on quitte sans faire d'histoire
			//...
			break; //sort du while
		}
		
		p2p_tcp_socket_close(sp, socket);
	
	}/* fin while */
	
	VERBOSE(sp,GUI,"\n");
	VERBOSE(sp,GUI,"GET::UPDATE::%d::100\n", numero_get);
	
	p2p_msg_delete(data_msg);
	
	return P2P_OK;
}

/***************************************************************************
Appelé par l'UI
Initie la récupération du fichier indiqué par search et result
***************************************************************************/
int p2p_get(server_params *sp, long int search, int result) {
	p2p_reply_item reply_item=NULL;
	int i;
	
	//récupération des infos du résultat
	if ( search < sp->p2pSearchFaits->length ) {
		//le numéro de recherche est valide
		if ( (reply_item=p2p_search_get(sp, search, result)) ==NULL ) {
			//on a pas trouvé le résultat demandé,
			//afficher kke chose et sortir proprement de la fonction sans tout planter
			return P2P_OK;
		}
		
		//récupération adresse noeud destinataire
		p2p_addr dest_addr = p2p_addr_create();
		p2p_addr_setstr(dest_addr, reply_item->noeud);
		
		//construction du message
		p2p_msg get_msg = p2p_msg_create();
		p2p_msg_init(get_msg, P2P_MSG_GET, P2P_MSG_TTL_ONE_HOP, sp->p2pMyId, dest_addr);
		
		//construction du payload avec les deux offset à zéro et le nom du fichier
		unsigned char get_payload[2*P2P_INT_SIZE+strlen(reply_item->name)+1];
		for (i=0; i<2*P2P_INT_SIZE; i++) get_payload[i]=0x00;
		memcpy(&get_payload[2*P2P_INT_SIZE], reply_item->name, strlen(reply_item->name)+1);
		p2p_msg_init_payload(get_msg, 2*P2P_INT_SIZE+strlen(reply_item->name)+1, get_payload);
		
		//on incrémente le nombre de get faits
		sp->get_faits++;
		//on envoie les infos sur le nouveau get à la GUI
		VERBOSE(sp,GUI,"\n");
		VERBOSE(sp,GUI,"GET::NEW::%s::%ld::0::%s\n",reply_item->name,reply_item->size,reply_item->noeud);
		
		// multi thread pour récupérer des fichiers de plus d'1MB
		if ( (reply_item->size)>1000000 ) {
			// Le fichier fait plus d'1MB, on crée un fils pour le récupérer
			pidFils=fork();
			if ( pidFils==-1 ) {
				// La création du fils a échoué, on récupère le fichier nous-même
				if ( p2p_file_create_file(sp, reply_item->name, (int)(reply_item->size))==P2P_OK) {
					p2p_get_data(sp, get_msg, reply_item->name, reply_item->size, sp->get_faits);
				} else {
					//la création du fichier a planté
					//afficher kke chose et sortir proprement de la fonction sans tout planter
					//...
				}
			}
			if (pidFils==0) { // le fils créé récupère le fichier
				if ( p2p_file_create_file(sp, reply_item->name, (int)(reply_item->size))==P2P_OK) {
					p2p_get_data(sp, get_msg, reply_item->name, reply_item->size, sp->get_faits);
				} else {
					//la création du fichier a planté
					//afficher kke chose et sortir proprement de la fonction sans tout planter
					//...
				}
				p2p_addr_delete(dest_addr);
				p2p_msg_delete(get_msg);
				sp_delete(sp);
				exit(0); //le fils meurt directement
			}
		} else {
			// Le fichier fait moins d'1MB, on le récupère directement
			if ( p2p_file_create_file(sp, reply_item->name, (int)(reply_item->size))==P2P_OK) {
					p2p_get_data(sp, get_msg, reply_item->name, reply_item->size, sp->get_faits);
				} else {
					//la création du fichier a planté
					//afficher kke chose et sortir proprement de la fonction sans tout planter
					//...
				}
		}
			
		p2p_addr_delete(dest_addr);
		p2p_msg_delete(get_msg);
		
	} else {
		// le numéro de recherche n'est pas valide
		//afficher kke chose et sortir proprement de la fonction sans tout planter
		return P2P_OK;
	}
	
	
	return P2P_OK;	
}

/***************************************************************************
Réception d'un message P2P_GET, envoi d'un P2P_DATA
Il faudrait vérifier qu'il ne demande pas plus de 64KB
***************************************************************************/
int p2p_get_received(server_params *sp, p2p_msg get_msg, int socket) {
	int i;
	unsigned short int payload_length=2*P2P_INT_SIZE;
	unsigned long int value=0;
	unsigned long int taille_fic=0;
	unsigned char status;
	unsigned char *octets_data;
	unsigned char *data_payload=(unsigned char*)malloc(sizeof(unsigned char)*2*P2P_INT_SIZE);
	
	//récupération des offsets et du nom de fichier
	long int begin_offset;
	memcpy(&begin_offset, p2p_get_payload(get_msg), P2P_INT_SIZE);
	begin_offset=ntohl(begin_offset);
	long int end_offset;
	memcpy(&end_offset, &(p2p_get_payload(get_msg)[P2P_INT_SIZE]), P2P_INT_SIZE);
	end_offset=ntohl(end_offset);
	char file_name[p2p_msg_get_length(get_msg)-2*P2P_INT_SIZE];
	memcpy(file_name, &(p2p_get_payload(get_msg)[2*P2P_INT_SIZE]), p2p_msg_get_length(get_msg)-2*P2P_INT_SIZE);
	
	//initialisation du message DATA
	p2p_msg data_msg = p2p_msg_create();
	p2p_msg_init(data_msg, P2P_MSG_DATA, P2P_MSG_TTL_ONE_HOP, p2p_addr_duplicate(sp->p2pMyId), p2p_addr_duplicate(p2p_msg_get_src(get_msg)));
	
	if (p2p_file_is_available(sp, file_name, &taille_fic)==P2P_OK ) {
		//le fichier est dispo
		status=P2P_DATA_OK;
		
		if (p2p_file_get_chunck(sp, file_name, (int)begin_offset, (int)end_offset, &octets_data)==P2P_OK){
			//on a réussi à prendre la partie du fichier
			value=end_offset-begin_offset+1;
			payload_length=(unsigned short int)(2*P2P_INT_SIZE+end_offset-begin_offset+1);
			data_payload=(unsigned char*)realloc(data_payload,sizeof(unsigned char)*payload_length);
			memcpy(&data_payload[2*P2P_INT_SIZE], octets_data, payload_length-2*P2P_INT_SIZE);
			free(octets_data);
	
		} else {
			//on a pas pu récupérer la partie du fichier
			status=P2P_DATA_ERROR;
			value=P2P_INTERNAL_SERVER_ERROR;
		}
		
	} else {
		//le fichier n'est pas disponible
		status=P2P_DATA_ERROR;
	}
	
	//remplissage payload
	data_payload[0]=status;
	for (i=0; i<P2P_INT_SIZE-1; i++) data_payload[i+1]=0x00;//trois octets qui comptent pour du beurre
	value=htonl(value);
	memcpy(&data_payload[P2P_INT_SIZE], &value, P2P_INT_SIZE);
	
	p2p_msg_init_payload(data_msg, payload_length, data_payload);
	
	//envoi du message
	p2p_tcp_msg_sendfd(sp, data_msg, socket);
	
	VERBOSE(sp,GUI,"\n");
	VERBOSE(sp,GUI,"DATA::FILE::%s::%ld::%ld::%ld::%s\n", file_name, taille_fic, 
			begin_offset, end_offset, p2p_addr_get_str(p2p_msg_get_dst(data_msg)));
	
	//free(octets_value);
	free(data_payload);
	p2p_msg_delete(data_msg);
	
	return P2P_OK;
}
